#include "./BSP/24CXX/bsp_24cxx.h"
#include <stdio.h>

#if USE_HARDWARE_I2C
#include "i2c.h"
#else
#include "./BSP/IIC/bsp_iic.h"
#endif

/**
 * @brief       初始化IIC接口
 * @param       无
 * @retval      无
 */
void at24cxx_init(void)
{
#if USE_HARDWARE_I2C
    /* 硬件I2C在CubeMX中初始化 */
#else
    iic_init();
#endif
}

/**
 * @brief       在AT24CXX指定地址读出一个数据
 * @param       readaddr: 开始读数的地址
 * @retval      读到的数据
 */
uint8_t at24cxx_read_one_byte(uint16_t addr)
{
#if USE_HARDWARE_I2C
    uint8_t temp = 0;
    uint8_t dev_addr;
    
    /* 根据不同的24CXX型号, 设置设备地址 */
    if (EE_TYPE > AT24C16)
    {
        dev_addr = 0xA0;
        /* 发送设备地址和高字节地址 */
        HAL_I2C_Mem_Write(&hi2c1, dev_addr, addr >> 8, I2C_MEMADD_SIZE_8BIT, &temp, 0, HAL_MAX_DELAY);
    }
    else
    {
        dev_addr = 0xA0 + ((addr >> 8) << 1);
    }
    
    /* 读取数据 */
    HAL_I2C_Mem_Read(&hi2c1, dev_addr, addr & 0xFF, I2C_MEMADD_SIZE_8BIT, &temp, 1, HAL_MAX_DELAY);
    
    return temp;
#else
    uint8_t temp = 0;
    iic_start();                /* 发送起始信号 */

    /* 根据不同的24CXX型号, 发送高位地址
     * 1, 24C16以上的型号, 分2个字节发送地址
     * 2, 24C16及以下的型号, 分1个低字节地址 + 占用器件地址的bit1~bit3位 用于表示高位地址, 最多11位地址
     *    对于24C01/02, 其器件地址格式(8bit)为: 1  0  1  0  A2  A1  A0  R/W
     *    对于24C04,    其器件地址格式(8bit)为: 1  0  1  0  A2  A1  a8  R/W
     *    对于24C08,    其器件地址格式(8bit)为: 1  0  1  0  A2  a9  a8  R/W
     *    对于24C16,    其器件地址格式(8bit)为: 1  0  1  0  a10 a9  a8  R/W
     *    R/W      : 读/写控制位 0,表示写; 1,表示读;
     *    A0/A1/A2 : 对应器件的1,2,3引脚(只有24C01/02/04/8有这些脚)
     *    a8/a9/a10: 对应存储整列的高位地址, 11bit地址最多可以表示2048个位置,可以寻址24C16及以内的型号
     */    
    if (EE_TYPE > AT24C16)      /* 24C16以上的型号, 分2个字节发送地址 */
    {
        iic_send_byte(0XA0);    /* 发送写命令, IIC规定最低位是0, 表示写入 */
        iic_wait_ack();         /* 每次发送完一个字节,都要等待ACK */
        iic_send_byte(addr >> 8);/* 发送高字节地址 */
    }
    else 
    {
        iic_send_byte(0XA0 + ((addr >> 8) << 1));   /* 发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */
    }
    
    iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */
    iic_send_byte(addr % 256);  /* 发送低位地址 */
    iic_wait_ack();             /* 等待ACK, 此时地址发送完成了 */
    
    iic_start();                /* 重新发送起始信号 */ 
    iic_send_byte(0XA1);        /* 进入接收模式, IIC规定最低位是1, 表示读取 */
    iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */
    temp = iic_read_byte(0);    /* 接收一个字节数据 */
    iic_stop();                 /* 产生一个停止条件 */
    return temp;
#endif
}

/**
 * @brief       在AT24CXX指定地址写入一个数据
 * @param       addr: 写入数据的目的地址
 * @param       data: 要写入的数据
 * @retval      无
 */
void at24cxx_write_one_byte(uint16_t addr, uint8_t data)
{
#if USE_HARDWARE_I2C
    uint8_t dev_addr;
    
    /* 根据不同的24CXX型号, 设置设备地址 */
    if (EE_TYPE > AT24C16)
    {
        dev_addr = 0xA0;
        /* 发送设备地址和高字节地址 */
        HAL_I2C_Mem_Write(&hi2c1, dev_addr, addr >> 8, I2C_MEMADD_SIZE_8BIT, &data, 0, HAL_MAX_DELAY);
    }
    else
    {
        dev_addr = 0xA0 + ((addr >> 8) << 1);
    }
    
    /* 写入数据 */
    HAL_I2C_Mem_Write(&hi2c1, dev_addr, addr & 0xFF, I2C_MEMADD_SIZE_8BIT, &data, 1, HAL_MAX_DELAY);
    
    HAL_Delay(10);  /* EEPROM写入需要等待 */
#else
    /* 原理说明见:at24cxx_read_one_byte函数, 本函数完全类似 */
    iic_start();                /* 发送起始信号 */

    if (EE_TYPE > AT24C16)      /* 24C16以上的型号, 分2个字节发送地址 */
    {
        iic_send_byte(0XA0);    /* 发送写命令, IIC规定最低位是0, 表示写入 */
        iic_wait_ack();         /* 每次发送完一个字节,都要等待ACK */
        iic_send_byte(addr >> 8);/* 发送高字节地址 */
    }
    else 
    {
        iic_send_byte(0XA0 + ((addr >> 8) << 1));   /* 发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */
    }
    
    iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */
    iic_send_byte(addr % 256);  /* 发送低位地址 */
    iic_wait_ack();             /* 等待ACK, 此时地址发送完成了 */
    
    /* 因为写数据的时候,不需要进入接收模式了,所以这里不用重新发送起始信号了 */
    iic_send_byte(data);        /* 发送1字节 */
    iic_wait_ack();             /* 等待ACK */
    iic_stop();                 /* 产生一个停止条件 */
    HAL_Delay(10);              /* 注意: EEPROM 写入比较慢,必须等到10ms后再写下一个字节 */
#endif
}
 
/**
 * @brief       检查AT24CXX是否正常
 * @note        检测原理: 在器件的末地址写如0X55, 然后再读取, 如果读取值为0X55
 *              则表示检测正常. 否则,则表示检测失败.
 *
 * @param       无
 * @retval      检测结果
 *              0: 检测成功
 *              1: 检测失败
 */
uint8_t at24cxx_check(void)
{
    uint8_t temp;
    uint16_t addr = EE_TYPE;
    temp = at24cxx_read_one_byte(addr); /* 避免每次开机都写AT24CXX */

    if (temp == 0X55)   /* 读取数据正常 */
    {
        return 0;
    }
    else    /* 排除第一次初始化的情况 */
    {
        at24cxx_write_one_byte(addr, 0X55); /* 先写入数据 */
        temp = at24cxx_read_one_byte(addr); /* 再读取数据 */

        if (temp == 0X55) /* 首次初始化成功 */
        {
            return 0;
        }
    }

    return 1;/* 检测失败 */
}

/**
 * @brief       在AT24CXX里面的指定地址开始读出指定个数的数据
 * @param       addr    : 开始读出的地址 对24c02为0~255
 * @param       pbuf    : 数据数组首地址
 * @param       datalen : 要读出数据的个数
 * @retval      无
 */
void at24cxx_read(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
    while (datalen--)
    {
        *pbuf++ = at24cxx_read_one_byte(addr++);
    }
}

/**
 * @brief       在AT24CXX里面的指定地址开始写入指定个数的数据
 * @param       addr    : 开始写入的地址 对24c02为0~255
 * @param       pbuf    : 数据数组首地址
 * @param       datalen : 要写入数据的个数
 * @retval      无
 */
void at24cxx_write(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
    while (datalen--)
    {
        at24cxx_write_one_byte(addr, *pbuf);
        addr++;
        pbuf++;
    }
}

/**
 * @brief       24CXX EEPROM读写测试函数
 * @param       无
 * @retval      0: 测试通过  1: 测试失败
 * @note        测试内容包括：器件检测、单字节读写、批量读写、数据校验、连续写入测试
 */
uint8_t at24cxx_test(void)
{
    uint8_t test_data[32];          /* 测试数据缓冲区 */
    uint8_t read_data[32];          /* 读取数据缓冲区 */
    uint16_t test_addr = 10;        /* 测试起始地址，避开检测地址 */
    uint16_t test_len = 16;         /* 测试数据长度 */
    uint16_t i;
    uint8_t error_count = 0;
    char *eeprom_type_str = "未知";
    uint16_t eeprom_size = EE_TYPE + 1;
    
    printf("=== 24CXX EEPROM 测试开始 ===\r\n");
    
    /* 1. 确定EEPROM型号 */
    switch(EE_TYPE)
    {
        case AT24C01:  eeprom_type_str = "AT24C01 (128字节)"; break;
        case AT24C02:  eeprom_type_str = "AT24C02 (256字节)"; break;
        case AT24C04:  eeprom_type_str = "AT24C04 (512字节)"; break;
        case AT24C08:  eeprom_type_str = "AT24C08 (1KB)"; break;
        case AT24C16:  eeprom_type_str = "AT24C16 (2KB)"; break;
        case AT24C32:  eeprom_type_str = "AT24C32 (4KB)"; break;
        case AT24C64:  eeprom_type_str = "AT24C64 (8KB)"; break;
        case AT24C128: eeprom_type_str = "AT24C128 (16KB)"; break;
        case AT24C256: eeprom_type_str = "AT24C256 (32KB)"; break;
        default: eeprom_type_str = "未知型号"; break;
    }
    
    printf("1. EEPROM型号: %s (容量:%d字节)\r\n", eeprom_type_str, eeprom_size);
    
    /* 2. 器件检测测试 */
    printf("2. 器件检测测试...\r\n");
    if (at24cxx_check() == 0)
    {
        printf("   器件检测成功\r\n");
    }
    else
    {
        printf("   器件检测失败，I2C通信可能存在问题\r\n");
        return 1;
    }
    
    /* 3. 单字节读写测试 */
    printf("3. 单字节读写测试...\r\n");
    uint8_t test_byte = 0xA5;
    uint16_t single_addr = 5;
    
    at24cxx_write_one_byte(single_addr, test_byte);
    HAL_Delay(10);  /* 等待写入完成 */
    
    uint8_t read_byte = at24cxx_read_one_byte(single_addr);
    
    if (read_byte == test_byte)
    {
        printf("   单字节测试成功 (地址:%d, 写入:0x%02X, 读取:0x%02X)\r\n", 
               single_addr, test_byte, read_byte);
    }
    else
    {
        printf("   单字节测试失败 (地址:%d, 写入:0x%02X, 读取:0x%02X)\r\n", 
               single_addr, test_byte, read_byte);
        return 1;
    }
    
    /* 4. 准备批量测试数据 */
    printf("4. 准备批量测试数据...\r\n");
    for (i = 0; i < test_len; i++)
    {
        test_data[i] = 0x10 + i;    /* 生成递增的测试数据 */
    }
    
    /* 5. 批量写入测试 */
    printf("5. 批量写入测试(地址:%d, 长度:%d)...\r\n", test_addr, test_len);
    at24cxx_write(test_addr, test_data, test_len);
    HAL_Delay(50);  /* 等待写入完成 */
    
    /* 6. 批量读取测试 */
    printf("6. 批量读取测试...\r\n");
    at24cxx_read(test_addr, read_data, test_len);
    
    /* 7. 数据校验 */
    printf("7. 数据校验...\r\n");
    error_count = 0;
    for (i = 0; i < test_len; i++)
    {
        if (read_data[i] != test_data[i])
        {
            error_count++;
            if (error_count <= 5)  /* 只显示前5个错误 */
            {
                printf("   数据校验失败，地址%d，期望:0x%02X，实际:0x%02X\r\n", 
                       test_addr + i, test_data[i], read_data[i]);
            }
        }
    }
    
    if (error_count == 0)
    {
        printf("   数据校验成功\r\n");
    }
    else
    {
        printf("   数据校验失败，错误数量: %d\r\n", error_count);
        return 1;
    }
    
    /* 8. 连续写入测试（测试页边界处理） */
    printf("8. 连续写入测试...\r\n");
    uint16_t continuous_addr = 50;
    uint16_t continuous_len = 20;
    
    /* 生成连续测试数据 */
    for (i = 0; i < continuous_len; i++)
    {
        test_data[i] = i;
    }
    
    at24cxx_write(continuous_addr, test_data, continuous_len);
    HAL_Delay(50);
    at24cxx_read(continuous_addr, read_data, continuous_len);
    
    error_count = 0;
    for (i = 0; i < continuous_len; i++)
    {
        if (read_data[i] != test_data[i])
        {
            error_count++;
        }
    }
    
    if (error_count == 0)
    {
        printf("   连续写入测试成功\r\n");
    }
    else
    {
        printf("   连续写入测试失败，错误数量: %d\r\n", error_count);
        return 1;
    }
    
    /* 9. 速度测试 */
    printf("9. 读写速度测试...\r\n");
    uint32_t start_time, end_time;
    uint32_t write_speed, read_speed;
    
    /* 写入速度测试 */
    start_time = HAL_GetTick();
    at24cxx_write(test_addr, test_data, test_len);
    end_time = HAL_GetTick();
    
    if (end_time > start_time)
    {
        write_speed = (test_len * 1000) / (end_time - start_time);
        printf("   写入速度: %d字节用时%ldms (约%ldB/s)\r\n", 
               test_len, end_time - start_time, write_speed);
    }
    else
    {
        printf("   写入速度: %d字节用时<1ms\r\n", test_len);
    }
    
    /* 读取速度测试 */
    start_time = HAL_GetTick();
    at24cxx_read(test_addr, read_data, test_len);
    end_time = HAL_GetTick();
    
    if (end_time > start_time)
    {
        read_speed = (test_len * 1000) / (end_time - start_time);
        printf("   读取速度: %d字节用时%ldms (约%ldB/s)\r\n", 
               test_len, end_time - start_time, read_speed);
    }
    else
    {
        printf("   读取速度: %d字节用时<1ms\r\n", test_len);
    }
    
    printf("=== 24CXX EEPROM 测试完成 - 所有测试通过 ===\r\n\r\n");
    return 0;
}

/**
 * @brief       24CXX EEPROM无损测试函数
 * @param       无
 * @retval      0: 测试通过  1: 测试失败
 * @note        测试前备份数据，测试后恢复，不破坏原有数据
 */
uint8_t at24cxx_safe_test(void)
{
    /* 定义测试会用到的所有地址范围 */
    #define BACKUP_START_ADDR   0       /* 备份起始地址 */
    #define BACKUP_SIZE         80      /* 备份大小：涵盖地址0-79 */
    
    uint8_t backup_data[BACKUP_SIZE];   /* 备份缓冲区 */
    uint8_t result = 0;
    
    printf("=== 24CXX EEPROM 无损测试开始 ===\r\n");
    
    /* 检查备份区域是否超出EEPROM容量 */
    if (BACKUP_START_ADDR + BACKUP_SIZE > EE_TYPE + 1)
    {
        printf("备份区域超出EEPROM容量，无法进行无损测试\r\n");
        return 1;
    }
    
    /* 1. 备份测试区域的原始数据 */
    printf("备份原始数据(地址%d-%d)...\r\n", 
           BACKUP_START_ADDR, BACKUP_START_ADDR + BACKUP_SIZE - 1);
    at24cxx_read(BACKUP_START_ADDR, backup_data, BACKUP_SIZE);
    
    /* 2. 执行完整的测试 */
    printf("执行完整功能测试...\r\n");
    result = at24cxx_test();
    
    /* 3. 恢复原始数据 */
    printf("恢复原始数据...\r\n");
    at24cxx_write(BACKUP_START_ADDR, backup_data, BACKUP_SIZE);
    HAL_Delay(100);  /* 等待写入完成 */
    
    if (result == 0)
    {
        printf("=== 无损测试完成 - 测试通过，数据已恢复 ===\r\n\r\n");
    }
    else
    {
        printf("=== 无损测试完成 - 测试失败，数据已恢复 ===\r\n\r\n");
    }
    
    return result;
    
    #undef BACKUP_START_ADDR
    #undef BACKUP_SIZE
}
